Effective java总结

考虑用静态工厂方法代替构造器

优势

  • 有名称,返回的内容更清楚,当一个类出现多个构造器时,为了避免调用错误的构造器,推荐使用静态工厂方法
  • 不必在每次调用时创建一个新的对象,这使得不可变类可以预先构建好实例,将预先构建好的实例缓存起来

ps:使用静态工厂方法返回的实例可以确保它是个单例或是不可实例化的,确保不会存在两个相等的实例,所以客户端直接可以使用==来代替equals(Object),来提高性能

  • 可以返回原返回类型的任何子类型的对象
  • 在创建参数化类型实例的时候,使代码更加简洁

Map<String, List<String>> m = new HashMap<String, List<String>>();可代替为Map<String, List<String>> m = HashMap.newInstance();

缺势

  • 如果不含公有的或者受保护的构造器,就不能被子类化
  • 它与其他静态方法实际上没有任何区别

静态工厂方法惯用的名称

  • valueOf返回的实例和它的参数具有相同的值
  • getInstance返回一个实例
  • newInstance确保返回的是一个新的实例
  • getType返回对象的类型

遇到多个构造器参数时要考虑用构建器

问题

当一个类有多个可选参数时,如果使用构造器则会出现编写难,阅读难的问题(比如20个可选参数,那么只使用构造器就会变成20个参数的构造器,19个参数的构造器…1个参数的构造器)

解决方法一

使用javaBeans的模式,首先调用一个无参构造器,再用setter方法设置每个必要的参数

缺点

当遇到多线程操作时,可能会出现处于不一致的状态,试图使用不一致的状态对象将会导致失败,需要确保它的线程安全

ps:

  • 线程A: 获取person,对其name age sex 就行set操作
  • 线程B: 获取person,对其进行get操作

这时候会出现一种情况,在线程A中没有set完毕,线程B 就开始取相应的属性,那么就会造成javabean出于不一致的状态

解决方法二

使用builder模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class builderTest {
private String name;
private Integer age;
private String sex;
private String phone;
private String adress;

private builderTest(builder builder) {
name = builder.name;
age = builder.age;
sex = builder.sex;
phone = builder.phone;
adress = builder.adress;
}
// 提供一个辅助的静态建造器builder
public static class builder {
private String name;
private Integer age;
private String sex;
private String phone;
private String adress;

public builder name(String val) {
name = val;
return this;
}

public builder age(Integer val) {
age = val;
return this;
}

public builder sex(String val) {
sex = val;
return this;
}

public builder phone(String val) {
phone = val;
return this;
}

public builder adress(String val) {
adress = val;
return this;
}

public builderTest build() {
return new builderTest(this);
}
}
}
1
builderTest.builder().adress("钱湖南路").age(18).name("ymt").phone("13806820032").sex("男").build();

优点

可以有多个可变的参数,更加的灵活,建议4个以上的参数时,使用builder,易于阅读和编写,比javaBeans更安全

用私有构造器或者枚举类型强化Singleton属性

实现单例方法(java 1.5之前)

两种方法都要把构造器私有化,并导出公有的静态成员

方法一

1
2
3
4
public class SingletonTest {
private SingletonTest(){...}
public static final SingletonTest Instance = new SingletonTest();
}

缺点是容易被通过反射机制setAccessible方法,调用私有构造器,解决方法是修改构造器,让它在被要求创建第二个实例的时候抛出异常

方法二

1
2
3
4
5
6
7
8
public class SingletonTest {
private SingletonTest(){...}
private static final SingletonTest Instance = new SingletonTest();

public static SingletonTest getInstance(){
return Instance;
}
}

使用工厂方法的优势在于,提供了灵活性,但是容易被修改,需要声明所有实例都是瞬时的,否则每次反序列化时都会创建一个新的实例,添加方法readResolve方法即可

实现单例方法(java 1.5之后)

方法三

使用枚举快速实现单例

1
2
3
public enum SingletonTest{
INSTANCE;
}

就是这么的简单,即可生成一个单例,它自动提供了序列化机制,防止了多次实例化,单元素的枚举类型已经成为实现单例的最佳方法

通过私有构造器强化不可实例化的能力

企图通过将类做成抽象类来强制该类不可被实例化,这是行不通的,该类可以被子类化,并且该子类也可以被实例化,因此最好的办法就是私有化构造器以达到其不可实例化的目的

避免创建不必要的对象

如果方法中没有必要每次都创建一个新对象,那么请将方法中的创建对象代码,移至该类的静态代码区域,那么该类创建的对象只会在初始化的时候创建

错误

正确

尽量避免使用终结方法finalize()

缺点

  • 不能保证会被及时的执行,注重时间的任务不应该由终结方法完成
  • 在不同的JVM平台上,表现不一样
  • 不仅不能保证finalize()方法会被及时的执行,而且根本就不保证他们会执行
  • 使用finalize()有一个非常严重的Severe性能损失

优点

  • 在本地对等体并不拥有关键资源的前提下(注是否因为关键资源可被本地其他进程使用),finalize()正是执行任务的最合适的工具
  • finalize()模式的实例中所示的四个类FileInputStream,FileOutputStream,Timer和Connection,都具有终结方法。当他们的显示的终止方法未能被调用的情况下,这些finalize()充当了安全网

覆盖equals时请遵守通用约定

什么时候应该覆盖Object.equals

如果类具有自己特有的”逻辑相等”概念(不等同于对象相同,指数值相同),而且超类还没有覆盖equals方法以实现期望的行为,这时我们需要覆盖该方法

特殊的“值类”

例如枚举类型,虽然他也是“值类”,但对于这样的类,逻辑相同和对象相同是同一回事,因此不需要覆盖Object.equals

高质量equals方法诀窍

  • 使用==操作符检查“参数是否为这个对象的引用”
  • 使用instanceof操作符检查“参数是否为正确类型”
  • 把参数转换成正确的类型
  • 对于该类中的每个“关键域”,检查参数中的域是否与该对象中对应的域相匹配
  • 当编写完成equals方法之后,问自己它是否对称,传递与一致
  • 覆盖equals时总要覆盖hashcode
  • 不要企图让equals方法过于智能
  • 不要将equals声明中的Object对象替换为其他类型

始终要覆盖toString

如果不覆盖toString方法,那么print方法打印出来的内容将难以理解

谨慎的覆盖clone

除非拷贝数据,一般不覆盖clone方法也不去调用它

考虑实现Comparable接口

实现Comparable<T>接口,覆盖其compareTo方法,用来判断2个值类的逻辑大小

使类和成员的可访问性最小化

良好的模块会隐藏所有的实现细节,把它的API和它的实现清晰的隔离开来,模块和模块之间只通过他们的API进行通信

关于private,protected,public和包级私有(缺省)的使用

  • 尽可能地使每个类或者成员不被外界访问

    • 对于顶层非嵌套类和接口,有包级私有(缺省)和公有(public)2种访问级别
    • 如果一个包级私有的顶层类或接口只是在某一个类的内部被用到,就应该考虑使它成为唯一使用它的那个类的私有嵌套类

  • 子类覆盖超类的方法,子类中的访问级别不允许低于超类中的访问级别,这样能够确保任何可使用超类的实例的地方也都可以使用子类的实例

  • 接口中所有的方法都隐含着公有访问级别

  • 实例域决不能是公有的

在公有类中使用访问方法而非公有域

公有类不应该直接暴露数据域,如果类可以在它所在的包的外部进行访问就提供访问方法,但是字段必须是私有化的

使可变性最小化

要实现不可变的类需要满足一下几点要求:

  • 不要提供任何会修改对象状态的方法。(setter方法)
  • 保证类不会被扩展。用final修饰类或者是私有化构造器提供公有静态工厂方法。
  • 使所有的域都是final的。
  • 使所有的域都成为私有的。
  • 确保对于任何可变组件的互斥访问。

复合优先于继承

复合设计

不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例

例如,你想要继承List类,那么可以用复合的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//新的类
public class ForwardingList<E> implements List<E> {
// 引用现有类的一个实例
private List<E> mList;
public ForwardingList(List<E> list) {
this.mList = list;
}
@Override
public boolean add(E object) {
return mList.add(object);
}
//其他的一些api
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InstrumentedList<E> extends ForwardingList<E> {

private int addCount = 0;

public InstrumentedList(List<E> list) {
super(list);
}

@Override
public boolean add(E object) {
addCount++;
return super.add(object);
}
}

ps:不是特别懂这块 先放放

接口优于抽象

二者不同处

  • 抽象类允许包含某些方法的实现,但是接口不允许
  • 为了实现由抽象类定义的类型,类必须成为抽象类的一个子类,因为java只允许单继承,所以抽象类受到了极大地限制
  • 两个类实现同一个接口很容易,但扩展同一个抽象类,就必须将抽象类放到类型层次的最高处,以便两个类的一个祖先成为他的子类
  • 接口是定义mixin(混合类型)的理想选择

ps:mixin类允许任选的功能可以被混合到类型的主要功能中,例如Comparable接口,它允许其他的可相互比较的对象进行排序

  • 抽象类比接口更容易修改

当需要新添方法时,抽象类可以始终增加具体的方法,但是接口则需要一个一个将实现了该接口的类修改,所以设计公有接口要非常的谨慎

使用的不同场景

接口是定义允许多个实现的类型的最佳途径,当演变的容易性比灵活性和功能更为重要时,应该使用抽象类来定义类型

接口只用于定义类型

接口中的常量都是静态的final域,不包含任何方法只有静态final常量的接口叫做常量接口常量接口模式是对接口的不良使用,(并不是接口不能添加常量,只是如果单单为了常量写一个接口不合适)会导致把这样的实现细节泄露到该类的导出API中

导出常量

如果这些常量和某个现有的类或者接口紧密相关,就应该把这些常量添加到这个类或者接口中如果这些常量最好被看作是枚举类型的成员,那就应该使用枚举类型来导出常量,否则需要使用不可实例化的工具类(私有化构造器)总而言之,接口应该只是用来定义类型,不应该用来导出常量

用函数对象表示策略(策略接口)

例如String类的源码用函数对象表示策略

1
2
3
4
5
6
7
8
9
10
11
12
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable {

public int compare(String s1, String s2) {//具体实现代码}

/** Replaces the de-serialized object. */
private Object readResolve() {return CASE_INSENSITIVE_ORDER; }
}

public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}

String类利用这种模式,通过它的CASE_INSENSITIVE_ORDER域,导出一个不区分大小写的字符串比较器

在java中实现这种模式,要声明一个接口来表示策略,并且为每个具体策略声明一个实现了该接口的类,当策略只被使用一次时,通常用匿名类来实例化,当该策略要重复使用时,该类通常要被实现为私有的静态成员类,通过静态final域被导出

优先考虑静态成员类

嵌套类

  • 静态成员类
  • 非静态成员类
  • 匿名类
  • 局部类

静态成员类(最简单的嵌套类)

它可以访问外围类的所有成员,常见的用法是作为公有的辅助类,仅当它的外部类一起使用时才有意义,比如Demo类里有一个静态成员类Week,Week类是星期一到星期日的枚举类,那么就可以Demo.Week.MONDAY来操作

1
2
3
4
5
6
7
8
public class staticDemo {

static class jingtai {
public void b(){
new staticDemo.jingtai().b();
}
}
}

非静态成员类

非静态成员类的每个实例都隐含着与外围类的一个外围实例相关联

1
2
3
4
5
6
7
8
public class nostaticDemo {

class nostatic{
public void c(){
new nostaticDemo().new nostatic().c();
}
}
}

匿名类

它并不与其他的成员一起被声明,而是在使用的同时被声明和实例化缺点是不能执行instanceof测试,任何需要命名类的其他事情,无法实现多个接口

  • 常见用法是动态的创建函数对象,比如Arrays类的sort方法调用
1
2
3
4
5
6
Arrays.sort(b, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return 0;
}
});
  • 另一种是创建过程对象,如Runnable,Thread,TimerTask实例
  • 第三种是用在静态工厂方法的内部

局部类(使用较少)

在任何可以声明局部变量的地方都能声明局部类

  • 局部类有名字,可被重复使用
  • 只有当局部类是在非静态环境中定义,才有外围实例,不能包含静态成员
  • 必须简短,否则影响可读性

总结

如果成员类的每个实例都需要一个指向

赏个🍗吧
0%